// Copyright (C) 2010  Joan Puigcerver Perez, <joapuipe@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <Database.hpp>

#include <ctime>
#include <queue>

Database::Database(std::ostream& logf, ost::Mutex& logm)
  :  movies(logf, logm), people(logf, logm), logfile(logf), log_mutex(logm)
{
}

Database::~Database()
{
  for (std::vector<Link*>::iterator it = links.begin(); it != links.end(); ++it)
    delete *it;
}

bool Database::load(void)
{
  char log_msg[50];
  time_t t;
  
  t = time(0);
  if( !movies.load(this) ) return false;
  t = time(0) - t;
  sprintf(log_msg, "Movies loaded in %u seconds", (unsigned int)t);
  print_log_line(log_msg, logfile, log_mutex);
  
  t = time(0);
  if( !people.load(this) ) return false;
  t = time(0) - t;
  sprintf(log_msg, "People loaded in %u seconds", (unsigned int)t);
  print_log_line(log_msg, logfile, log_mutex);

  return true;
}

bool Database::add_link(Movie * movie, Person * person, Work work)
{
  Link * link = new Link(movie,person,work);
  if( link == 0 ) return false;
  
  person->add_link(link);  
  movie->add_link(link);
  links.push_back(link);
  
  return true;
}

/* Calcula la distància entre dues persones, utilitzant un filtre. */
Database::DistanceResult Database::distance(const Person* source, const Person* target, const Filter filter) const
{
  std::queue<const Person*> queue;
  std::vector<uint32_t> distances( people.size() );
  std::vector<LinkPPair> predecesors( people.size() );
  std::vector<bool> seen_people( people.size() );
  std::vector<bool> seen_movies( movies.size() );

  for(uint32_t i = 0; i < people.size(); ++i) {
    distances[i] = 0xFFFFFFFF;
    seen_people[i] = false; 
  }

  for(uint32_t i = 0; i < movies.size(); ++i) {
    seen_movies[i] = false;
  }
      
  distances[source->id()] = 0;
  seen_people[source->id()] = true;
  queue.push( source );

  /* BFS */
  while( !seen_people[target->id()] && !queue.empty() ) {
    const Person * pers_a = queue.front();
    queue.pop();

    for(std::list<const Link*>::const_iterator mov_it = pers_a->begin(); mov_it != pers_a->end(); ++mov_it) { // Pel·lícules de la persona A
      const Movie * movie = (*mov_it)->movie();
      if ( seen_movies[movie->id()] || !filter.pass(*mov_it) || !filter.pass(movie) ) continue; // peli vista o link o peli no passen filtres.

      for(std::list<const Link*>::const_iterator pers_it = movie->begin(); pers_it != movie->end(); ++pers_it) {   // Persones en la pel·lícula
	const Person * pers_b = (*pers_it)->person();
	if ( seen_people[pers_b->id()] || !filter.pass(*pers_it) ) continue; // persona vista o link no passa filtres.

	distances[pers_b->id()] = distances[ pers_a->id() ] + 1;     
	predecesors[pers_b->id()] = LinkPPair(*mov_it, *pers_it);
	seen_people[pers_b->id()] = true;              // persona vista
	queue.push( pers_b );
      }

      seen_movies[movie->id()] = true;  // quan hem visitat tots els actors, hem acabat amb la peli
    }
  }

  /* Processem la sortida. */
  LinkPPairList path;
  if ( !seen_people[target->id()] ) {
    return DistanceResult(0xFFFFFFFF, path);
  } else {
    uint32_t src = source->id();
    uint32_t cur = target->id();
    while ( cur != src ) {
      path.push_back( predecesors[cur] );
      cur = predecesors[cur].first->person()->id();
    }

    path.reverse();    

    return DistanceResult(distances[target->id()], path);
  }  
}

void Database::PureBFS(const Person * source, std::vector<uint32_t>& distances, const uint32_t max_dist, const Filter filter) const
{
  std::queue<const Person*> queue;
  std::vector<bool> seen_people( people.size() );
  std::vector<bool> seen_movies( movies.size() );

  for(uint32_t i = 0; i < people.size(); ++i) {
    distances[i] = 0xFFFFFFFF;
    seen_people[i] = false; 
  }

  for(uint32_t i = 0; i < movies.size(); ++i) {
    seen_movies[i] = false;
  }
  
  distances[source->id()] = 0;
  seen_people[source->id()] = true;
  queue.push( source );
  
  /* BFS */
  while( !queue.empty() ) {
    const Person * pers_a = queue.front();
    queue.pop();

    for(std::list<const Link*>::const_iterator mov_it = pers_a->begin(); mov_it != pers_a->end(); ++mov_it) { // Pel·lícules de la persona A
      const Movie * movie = (*mov_it)->movie();
      if ( seen_movies[movie->id()] || !filter.pass(*mov_it) || !filter.pass(movie) ) continue; // peli vista o link o peli no passen filtres.

      for(std::list<const Link*>::const_iterator pers_it = movie->begin(); pers_it != movie->end(); ++pers_it) {   // Persones en la pel·lícula
	const Person * pers_b = (*pers_it)->person();
	if ( seen_people[pers_b->id()] || !filter.pass(*pers_it) ) continue; // persona vista o link no passa filtres.

	distances[pers_b->id()] = distances[ pers_a->id() ] + 1;     
	seen_people[pers_b->id()] = true;              // persona vista
	if ( distances[pers_b->id()] < max_dist ) 
	  queue.push( pers_b );
      }
      
      seen_movies[movie->id()] = true;  // quan hem visitat tots els actors, hem acabat amb la peli
    }
  }
}

float Database::center(const Person * source, const Filter filter) const
{
  std::vector<uint32_t> distances( people.size() );
  PureBFS(source, distances, 0xFFFFFFFF, filter);

  float sum = 0.0f; uint32_t noninf = 0;
  for(size_t i = 0; i < distances.size(); ++i)
    if ( i != source->id() && distances[i] != 0xFFFFFFFF ) {
      sum += (float)distances[i];
      ++noninf;
    }
  
  if ( noninf == 0 ) return 0x7F800000;
  else return sum/(float)noninf;
}

std::list<const Person *> Database::distance_n(const Person * source, const uint32_t dist, 
					       const Filter filter) const
{
  std::vector<uint32_t> distances( people.size() );
  PureBFS(source, distances, dist, filter);

  std::list<const Person*> people_at_dist_n;
  for(size_t i = 0; i < distances.size(); ++i)
    if ( distances[i] == dist )
      people_at_dist_n.push_back( people[i] );
  
  return people_at_dist_n;
}

std::pair< bool, std::list<const Person *> > Database::search_person(const std::string& name) const
{
  return people.search( name );
}
